/**
 * Copyright (C) 2011 Shih-Yuan Lee (FourDollars) <fourdollars@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using HMAC;
using Soup;

namespace OAuth {
    public class Client : Object {
        private string consumer_key = null;
        private string consumer_secret = null;
        private string realm = null;
        private string request_token_key = null;
        private string request_token_secret = null;
        private string token_key = null;
        private string token_secret = null;
        private string signature_method = null;
        private string version = null;
        private Session session = null;

        public Client(
            string  oauth_consumer_key,
            string  oauth_consumer_secret,
            string? oauth_token_key = null,
            string? oauth_token_secret = null,
            string? oauth_realm = null,
            string  oauth_signature_method = "HMAC-SHA1",
            string  oauth_version = "1.0")
        {
            consumer_key = oauth_consumer_key;
            consumer_secret = oauth_consumer_secret;
            signature_method = oauth_signature_method;
            version = oauth_version;
            realm = oauth_realm ?? "";

            session = new Soup.SessionAsync();

            token_key = oauth_token_key;
            token_secret = oauth_token_secret;
        }

        private string get_signature(string key, string signature_base_string) {
            uchar[] hmac;
            HMAC.sha1(key, signature_base_string, out hmac);
            return URI.encode(Base64.encode(hmac), "+");
        }

        public string? request_token(string url, out string? raw = null, out int? status = null) {
            ulong oauth_nonce = (ulong) Random.next_int();
            ulong oauth_timestamp = (ulong) time_t();

            var buffer = new StringBuilder();

            buffer.printf(
                "oauth_consumer_key=%s&oauth_nonce=%lu&oauth_signature_method=%s&oauth_timestamp=%lu&oauth_version=%s",
                consumer_key, oauth_nonce, signature_method, oauth_timestamp, version);

            string parameters = URI.encode(buffer.str, "&=");
            buffer.printf("POST&%s&%s", URI.encode(url, null), parameters);

            string signature_base_string = buffer.str;
            string signature = get_signature(consumer_secret + "&", signature_base_string);

            buffer.printf("OAuth realm=\"%s\", ", realm);
            buffer.append_printf("oauth_version=\"%s\", ", version);
            buffer.append_printf("oauth_nonce=\"%lu\", ", oauth_nonce);
            buffer.append_printf("oauth_timestamp=\"%lu\", ", oauth_timestamp);
            buffer.append_printf("oauth_consumer_key=\"%s\", ", consumer_key);
            buffer.append_printf("oauth_signature_method=\"%s\", ", signature_method);
            buffer.append_printf("oauth_signature=\"%s\"", signature);

            var msg = new Message("POST", url);
            msg.request_headers.append("Authorization", buffer.str);
            msg.request_headers.append("Content-Type", "application/x-www-form-urlencoded");
            msg.request_headers.append("User-Agent", "gwibber-service-plurk");

            status = (int) session.send_message(msg);
            raw = (string) msg.response_body.data;

            if (status != 200) {
                debug("Signature Base String: %s", signature_base_string);
                debug("Authorization: %s", buffer.str);
                debug("Response Body: %s", raw);
                return null;
            }

            HashTable<string,string> form = Form.decode(raw);

            request_token_key = form.lookup("oauth_token");
            request_token_secret = form.lookup("oauth_token_secret");

            if (form.lookup("oauth_callback_confirmed") != "true") {
                warning("The server is using OAuth 1.0 instead of OAuth 1.0a.");
            }

            if (request_token_key != null && request_token_secret != null) {
                return request_token_key;
            }

            debug("%s", raw);

            return null;
        }

        public bool access_token(string url, string code, out string? raw = null, out int? status = null) {
            ulong oauth_nonce = (ulong) Random.next_int();
            ulong oauth_timestamp = (ulong) time_t();

            var buffer = new StringBuilder();

            buffer.printf(
                "oauth_consumer_key=%s&oauth_nonce=%lu&oauth_signature_method=%s&oauth_timestamp=%lu&oauth_token=%s&oauth_token_secret=%s&oauth_verifier=%s&oauth_version=%s",
                consumer_key, oauth_nonce, signature_method, oauth_timestamp, request_token_key, request_token_secret, code, version);

            string parameters = URI.encode(buffer.str, "&=");
            buffer.printf("POST&%s&%s", URI.encode(url, null), parameters);

            string signature_base_string = buffer.str;
            string signature = get_signature(consumer_secret + "&" + request_token_secret, signature_base_string);

            buffer.printf("OAuth realm=\"%s\", ", realm);
            buffer.append_printf("oauth_version=\"%s\", ", version);
            buffer.append_printf("oauth_verifier=\"%s\", ", code);
            buffer.append_printf("oauth_nonce=\"%lu\", ", oauth_nonce);
            buffer.append_printf("oauth_timestamp=\"%lu\", ", oauth_timestamp);
            buffer.append_printf("oauth_consumer_key=\"%s\", ", consumer_key);
            buffer.append_printf("oauth_token=\"%s\", ", request_token_key);
            buffer.append_printf("oauth_token_secret=\"%s\", ", request_token_secret);
            buffer.append_printf("oauth_signature_method=\"%s\", ", signature_method);
            buffer.append_printf("oauth_signature=\"%s\"", signature);

            var msg = new Message("POST", url);
            msg.request_headers.append("Authorization", buffer.str);
            msg.request_headers.append("Content-Type", "application/x-www-form-urlencoded");
            msg.request_headers.append("User-Agent", "gwibber-service-plurk");

            status = (int) session.send_message(msg);
            raw = (string) msg.response_body.data;

            if (status != 200) {
                debug("Signature Base String: %s", signature_base_string);
                debug("Authorization: %s", buffer.str);
                debug("Response Body: %s", raw);
                return false;
            }

            HashTable<string,string> form = Form.decode(raw);

            token_key = form.lookup("oauth_token");
            token_secret = form.lookup("oauth_token_secret");

            if (token_key == null || token_secret == null) {
                debug("%s", (string) msg.response_body.data);
                return false;
            }

            return true;
        }

        public string get_token() {
            return token_key;
        }

        public string get_token_secret() {
            return token_secret;
        }

        private string get_signature_base_string(string url, HashTable<string,string> parameters) {
            var buffer = new StringBuilder();

            List<unowned string> keys = parameters.get_keys();
            keys.sort((CompareFunc)strcmp);

            foreach (unowned string key in keys) {
                unowned string? val = parameters.lookup(key);
                string key_encoded = URI.encode(key, "&=()");

                if (buffer.len != 0) {
                    buffer.append("&");
                }

                if (val == null) {
                    buffer.append(key_encoded + "=");
                } else {
                    string val_encoded = URI.encode(val, "&=()");
                    buffer.append(key_encoded + "=" + val_encoded);
                }
            }

            var signature_base_string = new StringBuilder();
            signature_base_string.printf("POST&%s&%s", URI.encode(url, null), URI.encode(buffer.str, "&="));

            return signature_base_string.str;
        }

        private string get_query_string(HashTable<string,string> query) {
            var buffer = new StringBuilder();

            List<unowned string> keys = query.get_keys();
            keys.sort((CompareFunc)strcmp);

            foreach (unowned string key in keys) {
                unowned string? val = query.lookup(key);
                string key_encoded = URI.encode(key, "&=()");

                if (buffer.len != 0) {
                    buffer.append("&");
                }

                if (val == null) {
                    buffer.append(key_encoded + "=");
                } else {
                    string val_encoded = URI.encode(val, "&=()");
                    buffer.append(key_encoded + "=" + val_encoded);
                }
            }

            buffer.prepend("?");

            return buffer.str;
        }

        public string? request(string url, HashTable<string,string>? query = null, out int? status = null) {
            ulong oauth_nonce = (ulong) Random.next_int();
            ulong oauth_timestamp = (ulong) time_t();

            string query_string = null;
            if (query != null) query_string = get_query_string(query);

            var parameters = query ?? new HashTable<string,string>(str_hash, str_equal);

            parameters.replace("oauth_consumer_key", consumer_key);
            parameters.replace("oauth_nonce", oauth_nonce.to_string());
            parameters.replace("oauth_signature_method", signature_method);
            parameters.replace("oauth_timestamp", oauth_timestamp.to_string());
            parameters.replace("oauth_token", token_key);
            parameters.replace("oauth_version", version);

            string signature_base_string = get_signature_base_string(url, parameters);

            string signature = get_signature(consumer_secret + "&" + token_secret, signature_base_string);

            var buffer = new StringBuilder();

            buffer.printf("OAuth realm=\"%s\", ", realm);
            buffer.append_printf("oauth_version=\"%s\", ", version);
            buffer.append_printf("oauth_nonce=\"%lu\", ", oauth_nonce);
            buffer.append_printf("oauth_timestamp=\"%lu\", ", oauth_timestamp);
            buffer.append_printf("oauth_consumer_key=\"%s\", ", consumer_key);
            buffer.append_printf("oauth_token=\"%s\", ", token_key);
            buffer.append_printf("oauth_signature_method=\"%s\", ", signature_method);
            buffer.append_printf("oauth_signature=\"%s\"", signature);

            var msg = new Message("POST", url + query_string);
            msg.request_headers.append("Authorization", buffer.str);
            msg.request_headers.append("Content-Type", "application/x-www-form-urlencoded");
            msg.request_headers.append("User-Agent", "gwibber-service-plurk");

            status = (int) session.send_message(msg);

            if (status != 200) {
                debug("Signature Base String: %s", signature_base_string);
                debug("Authorization: %s", buffer.str);
                debug("Response Body: %s", (string) msg.response_body.data);
            }

            return (string) msg.response_body.data;
        }
    }
}
/* -*- coding: utf-8; indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- */
/* vim:set fileencodings=utf-8 tabstop=4 expandtab shiftwidth=4 softtabstop=4: */
